package cate.game.pvp.pointrace.handler;

import cate.common.table.d.*;
import cate.game.GameBody;
import cate.game.event.pointrace.PointRaceTimerEvent;
import cate.game.framework.ThreadShare;
import cate.game.framework.advanced.AdvancedLongAttributes;
import cate.game.framework.common.compress.CompressedContent;
import cate.game.framework.common.compress.PlaySideCompressHelper;
import cate.game.mail.po.MailBuilder;
import cate.game.play.part.PlaySide;
import cate.game.play.proce.play.PlayPO;
import cate.game.pvp.pointrace.PointRaceDataManager;
import cate.game.pvp.pointrace.data.*;
import cate.game.pvp.pointrace.handler.PointRaceOpQuery.QueryParam;
import cate.game.res.MixRes;
import cate.game.res.MixResItem;
import cate.game.role.Role;
import cate.game.role.fight.FuncPosition;
import cate.game.role.mail.Mail;
import cate.game.role.pvp.PvpRecord;
import cate.game.role.pvp.pointrace.msg.PointRaceDataResp;
import cate.game.role.pvp.pointrace.msg.PointRaceGetPsReq;
import cate.game.role.pvp.pointrace.msg.PointRaceGetPsResp;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import cp.solution.business.game.component.NoticeParam;
import easy.java.practice.errorcode.EcResult;
import org.springframework.util.CollectionUtils;
import reactor.core.publisher.Flux;

import javax.annotation.Nullable;
import java.util.List;
import java.util.Objects;

import static cate.game.client.msg.MsgSender.send;
import static cate.game.event.pointrace.PointRaceTimerEvent.EventType.RANK_DAY_RWD;
import static cate.game.event.pointrace.PointRaceTimerEvent.EventType.RANK_SEASON_RWD;
import static cate.game.pvp.pointrace.PointRaceManager.MODULE_NAME;

public class LocalPointRaceHandler extends PointRaceHandler {

    private final PointRaceOpQuery opQuery;
    private final PointRacePsOperation psOperation;
    private final PointRaceChallenge challenge;
    private final PointRaceRankHandler rankHandler;
    private final PointRaceRankRwdOperation rankRwdOperation;

    public LocalPointRaceHandler(GameBody gameBody,
                                 PointRaceDataManager manager,
                                 PointRaceOpQuery opQuery,
                                 PointRacePsOperation psOperation,
                                 PointRaceChallenge challenge,
                                 PointRaceRankHandler rankHandler,
                                 PointRaceRankRwdOperation rankRwdOperation) {
        super(gameBody, manager);
        this.opQuery = Objects.requireNonNull(opQuery);
        this.psOperation = Objects.requireNonNull(psOperation);
        this.challenge = Objects.requireNonNull(challenge);
        this.rankHandler = Objects.requireNonNull(rankHandler);
        this.rankRwdOperation = Objects.requireNonNull(rankRwdOperation);
    }

    @Override
    public EcResult<List<PointRaceOpponent>> findOpponents(Role role) {
        EcResult<List<PointRaceOpponent>> r = new EcResult<>();
        long now = System.currentTimeMillis();
        if (now - role.getPvp().pointRace.lastFindOpponentsTime < GDPointRace.FIND_OPPONENTS_INTERVAL) {
            return r.fail("请稍等片刻再刷新对手吧");
        }

        QueryParam param = QueryParam.of(role.getUid(), role.getPvp().pointRace.points, 5);
        List<PointRaceOpponent> opponents = this.opQuery.query(param);

        r.data = opponents;

        role.getPvp().pointRace.refreshOpponents(opponents);
        return r;
    }

    @Override
    protected PointRacePs findPs(String oppUid) {
        return this.psOperation.findOne(oppUid);
    }

    @Override
    public int findRank(Role role) {
        return this.rankHandler.getIndex(role.getUid());
    }

    @Override
    public int findRank(String roleUid) {
        return this.rankHandler.getIndex(roleUid);
    }

    @Override
    public EcResult<Void> admire(String uid, int rank) {
        return this.rankHandler.admire(uid, rank);
    }

    @Override
    public int getIndex(String uid) {
        return this.rankHandler.getIndex(uid);
    }

    @Nullable
    @Override
    public PointRaceRank getData(String uid) {
        return this.rankHandler.getData(uid);
    }

    @Override
    public Flux<PointRaceRank> getRankFlow() {
        return this.rankHandler.getRankFlow();
    }

    @Override
    public Flux<PointRacePs> getRankPsFlow() {
        return this.rankHandler.getRankPsFlow();
    }

    @Override
    public void updatePosition(Role role, FuncPosition funcPosition) {
        updatePosition(role, funcPosition.funcId);
    }

    private EcResult<Void> updatePosition(Role role, int funcId) {
        EcResult<Void> vr = new EcResult<>();
        EcResult<PlaySide> r = getGameBody().fight.side.createPlaySideByRole(role, funcId, 1);
        if (r.ok() && Objects.nonNull(r.data)) {
            PlaySide ps = r.data;
            ps.snapshot.activeTime = 0;
            EcResult<PointRacePs> psr = PointRacePs.compressEc(r.data, role.getPvp().pointRace.points);
            if (psr.ok() && Objects.nonNull(psr.data)) {
                this.psOperation.update(psr.data);
            }
        } else {
            return vr.fail("更新积分赛防守阵容失败");
        }
        return vr.success();
    }

    @Override
    protected EcResult<Void> challengeCheck(Role role, String oppUid, boolean skip) {
        EcResult<Void> r = super.challengeCheck(role, oppUid, skip);
        if (!r.ok()) {
            return r;
        }
        return updatePosition(role, GDFunc.POINT_RACE_DEF);
    }

    @Override
    public void whenChallengeWin(PointRaceOpponent oppA, PointRacePs psB, PlayPO playPO) {
        synchronized (this.challenge) {
            PointRacePs psA = findPs(oppA.getSnapshot().uid);
            PointRacePs dpsB = findPs(psB.getUid());
            PointRaceOpponent oppB = dpsB.getOpponent();
            PointRaceChallenge.ChallengeParam param = PointRaceChallenge.ChallengeParam.of(psA.getOpponent(), oppB, playPO.winner);
            PvpRecord record = this.challenge.handleChallenge(param);
            record.replayUid = playPO.uid;
            record.recordTime = playPO.startTime;
            Role roleA = getGameBody().role.getRole(oppA.getSnapshot().uid);
            if (playPO.winner == GDFight.Team.A) {
                psA.getOpponent().setPoints(record.valueA);
                record.rankA = rankHandler.update(oppA, record.valueA);
                if (!oppB.getSnapshot().unreal) {
                    dpsB.getOpponent().setPoints(record.valueB);
                    record.rankB = rankHandler.update(oppB, record.valueB);
                }
                if (Objects.nonNull(roleA)) {
                    roleA.getPvp().pointRace.points = record.valueA;
                }
                if (!oppB.getSnapshot().unreal) {
                    Role roleB = getGameBody().role.getRole(psB.getUid());
                    if (Objects.nonNull(roleB)) {
                        syncRolePoints(roleB);
                    }
                }
            } else {
                record.valueChangeA = 0L;
                record.valueChangeB = 0L;
                record.rankA = rankHandler.getIndex(oppA.getSnapshot().uid);
                if (!oppB.getSnapshot().unreal) {
                    record.rankB = rankHandler.getIndex(oppB.getSnapshot().uid);
                }
            }

            if (Objects.nonNull(roleA)) {
                roleA.getPvp().pointRace.addRecord(record, true);
                roleA.getPvp().pointRaceMark.updateMark(record.rankA, record.valueA);
                addRecord(record);
            }

            if (!oppB.getSnapshot().unreal) {
                Role roleB = getGameBody().role.getRole(psB.getUid());
                if (Objects.nonNull(roleB)) {
                    roleB.getPvp().pointRace.addRecord(record, false);
                }
            }
        }
    }

    @Override
    public void syncRolePoints(Role role) {
        PointRacePs ps = findPs(role.getUid());
        if (Objects.nonNull(ps)) {
            ThreadShare.instance().exe(role.getCc(), () ->
                    role.getPvp().pointRace.points = ps.getOpponent().getPoints());
        }
    }

    public void addRecord(PvpRecord record) {
        if (record.rankA <= RANK_LIMIT || record.rankB <= RANK_LIMIT) {
            ThreadShare.instance().getExecutor(MODULE_NAME.hashCode())
                    .execute(() -> getManager().getData().addRecord(record));
        }
    }

    @Override
    public EcResult<PointRaceGetPsResp> handleGetPsReq(Role role, PointRaceGetPsReq req) {
        EcResult<PointRaceGetPsResp> r = super.handleGetPsReq(role, req);
        PointRacePs ps = psOperation.findOne(req.uid);
        if (Objects.isNull(ps)) {
            return r.fail("找不到对手阵容!");
        }

        CompressedContent cc = ps.getCps();
        EcResult<PlaySide> pr = PlaySideCompressHelper.uncompressEc(cc);
        if (!pr.ok()) {
            return r.fail("解压对手阵容出错!");
        }

        r.data = new PointRaceGetPsResp(pr.data);

        return r.success();
    }

    @Override
    public void onRankDayRwdEvent(PointRaceTimerEvent.RankDayRwdEvent rankDayRwdEvent) {
        PointRaceKV kv = getManager().getData();
        AdvancedLongAttributes attributes = kv.getEventAttributes();
        if (attributes.hasAttribute(RANK_DAY_RWD.getFlag() + rankDayRwdEvent.getDay())) {
            return;
        }

        PointRaceRankRwdOperation.QueryParam param = PointRaceRankRwdOperation.QueryParam.ofDay(
                getManager().getData().getSeason(),
                getGameBody().config.srvId,
                rankDayRwdEvent.getDay()
        );

        List<PointRaceRankRwd> queryList = rankRwdOperation.query(param);

        if (CollectionUtils.isEmpty(queryList)) {
            log.error("积分赛发放每日奖励列表为空! 第 {} 天!", rankDayRwdEvent.getDay());
            attributes.setAttribute(RANK_DAY_RWD.getFlag() + rankDayRwdEvent.getDay());
            return;
        }

        for (PointRaceRankRwd rwd : queryList) {
            int starMax = getGameBody().pvp.status.uidToStarMax.getOrDefault(rwd.getUid(), 0);
            int extra = getGameBody().table.pvpRankPointExtra.base.getExtraParam(starMax);
            Mail mail;
            if (extra > 0) {
                MixRes res = new MixRes(rwd.getReward());
                long count = res.count(GDObj.Type.NUM, GDObj.Num.RACE_POINT);
                long addNum = count * extra / GD.W_10000;
                MixResItem item = new MixResItem(GDObj.Type.NUM, GDObj.Num.RACE_POINT, addNum);
                res.addItem(item);
                mail = new MailBuilder()
                        .setContentId(GDMail.Tid.POINT_RACE_DAILY_REWARD_EX)
                        .setNoticeParamList(Lists.newArrayList(
                                new NoticeParam(rwd.getRank()),
                                new NoticeParam(starMax),
                                new NoticeParam((extra / 100.0) + "%"),
                                new NoticeParam(NoticeParam.NoticeType.MIX_RES, new MixRes().addItem(item))))
                        .setReward(res)
                        .build();
            } else {
                mail = new MailBuilder()
                        .setContentId(GDMail.Tid.POINT_RACE_DAILY_REWARD)
                        .setParamList(Lists.newArrayList(rwd.getRank()))
                        .setReward(new MixRes(rwd.getReward()))
                        .build();
            }

            getGameBody().mail.send(rwd.getUid(), mail);

            if (rwd.getRank() <= 100) {
                Role role = getGameBody().role.findOnline(rwd.getUid());
                if (Objects.nonNull(role)) {
                    role.getHistory().action.addPointRaceRank(rwd.getRank());
                    role.getHistory().action.pointRaceRankTimes(rwd.getRank());
                } else {
                    kv.getDailyRankRd().computeIfAbsent(rwd.getDay(), m -> Maps.newConcurrentMap());
                    kv.getDailyRankRd().get(rwd.getDay()).put(rwd.getUid(), rwd.getRank());
                }
            }
        }

        attributes.setAttribute(RANK_DAY_RWD.getFlag() + rankDayRwdEvent.getDay());
    }

    @Override
    public void onRankSeasonDayRwdEvent(PointRaceTimerEvent.RankSeasonRwdEvent rankSeasonRwdEvent) {
        PointRaceKV kv = getManager().getData();
        AdvancedLongAttributes attributes = kv.getEventAttributes();
        if (attributes.hasAttribute(RANK_SEASON_RWD.getFlag())) {
            return;
        }

        PointRaceRankRwdOperation.QueryParam param = PointRaceRankRwdOperation.QueryParam.ofSeason(
                getManager().getData().getSeason(),
                getGameBody().config.srvId,
                rankSeasonRwdEvent.getDay()
        );
        List<PointRaceRankRwd> queryList = rankRwdOperation.query(param);

        if (CollectionUtils.isEmpty(queryList)) {
            log.error("积分赛发放赛季奖励为空! 第 {} 赛季", getManager().getData().getSeason());
            attributes.setAttribute(RANK_SEASON_RWD.getFlag());
            return;
        }

        for (PointRaceRankRwd rwd : queryList) {
            int starMax = getGameBody().pvp.status.uidToStarMax.getOrDefault(rwd.getUid(), 0);
            int extra = getGameBody().table.pvpRankPointExtra.base.getExtraParam(starMax);
            Mail mail;
            if (extra > 0) {
                MixRes res = new MixRes(rwd.getReward());
                long count = res.count(GDObj.Type.NUM, GDObj.Num.RACE_POINT);
                long addNum = count * extra / GD.W_10000;
                MixResItem item = new MixResItem(GDObj.Type.NUM, GDObj.Num.RACE_POINT, addNum);
                res.addItem(item);
                mail = new MailBuilder()
                        .setContentId(GDMail.Tid.POINT_RACE_SEASON_REWARD_EX)
                        .setNoticeParamList(Lists.newArrayList(
                                new NoticeParam(rwd.getRank()),
                                new NoticeParam(starMax),
                                new NoticeParam((extra / 100.0) + "%"),
                                new NoticeParam(NoticeParam.NoticeType.MIX_RES, new MixRes().addItem(item))))
                        .setReward(res)
                        .build();
            } else {
                mail = new MailBuilder()
                        .setContentId(GDMail.Tid.POINT_RACE_SEASON_REWARD)
                        .setParamList(Lists.newArrayList(rwd.getRank()))
                        .setReward(new MixRes(rwd.getReward()))
                        .build();
            }
            getGameBody().mail.send(rwd.getUid(), mail);
        }

        attributes.setAttribute(RANK_SEASON_RWD.getFlag());
    }

    @Override
    public void onOpenEvent(PointRaceTimerEvent.OpenEvent openEvent) {
        getGameBody().role.activeForOnline(
                role -> send(role::sendNow, new PointRaceDataResp(
                        role.getPvp().pointRace)));
    }
}
